package net.dotool;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * 用于配置指定配置,自动动态加载JAR包到classpath环境变量中.<BR>
 * 使用示例:<BR>
 * 
 * <pre>
 * public class AppMain {
 * 	public static void main(String...args){
 * 		//attach libs
 * 		ClassPathLibrary.attachLibraries(AppMain.class.getClassLoader());
 * 		or
 * 		ClassPathLibrary.attachLibraries(AppMain.class.getClassLoader(),&quot;C:/myapp/common-libs/&quot;);
 * 		String targetApp = args[0];//such as &quot;example.nodepend.MyAppExample&quot;
 * 		Class.forName(targetApp).newInstance();//run real target app
 * 	}
 * }
 * </pre>
 * 
 */
public final class ClassPathTools {
	/**
	 * 可以通过启动时系统级参数来控制在加载JAR包失败时是否打印错误日志,<br>
	 * 加入参数:java ... -Dcp.library.log=[true|false] -cp ...
	 */
	private static final boolean showLog;
	/**
	 * 允许启动程序指定适合于当前环境的配置文件,默认使用文件"classpath.library"<br>
	 * 加入参数:java ... -Dcp.library=[filename in current dir] -cp ...
	 */
	private static final String libraryFile;
	static {
		showLog = "true".equals(System.getProperty("cp.library.log", "false"));
		libraryFile = System.getProperty("cp.library", "classpath.library");
	}

	private static Method addUrlMethod;
	static {
		try {
			addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
			addUrlMethod.setAccessible(true);
		} catch (final Exception e) {
		}
	}

	/**
	 * never instance
	 */
	private ClassPathTools() {
	}

	/**
	 * 以当前程序运行的主目录为基础,附加libraryFile中指定的目录/文件到指定classLoader中
	 * 
	 * @see #attachLibraries(ClassLoader, String)
	 * @param classLoader
	 *            要添加JAR包的目标ClassLoader
	 */
	public static final void attachLibraries(final ClassLoader classLoader) {
		ClassPathTools.attachLibraries((URLClassLoader) classLoader, "./");
	}

	/**
	 * 以目录basePath为基础,附加libraryFile中指定的目录/文件到指定classLoader中
	 * 
	 * @param classLoader
	 * @param basePath
	 */
	public static final void attachLibraries(final ClassLoader classLoader, final String basePath) {
		ClassPathTools.attachLibraries((URLClassLoader) classLoader, basePath);
	}

	/**
	 * 允许调用者指定一些需要扫描的路径.直接将这些路径下的所有Jar文件加入到给定的classLoader中
	 * @param classLoader
	 * @param paths
	 */
	public static final void attachPathLibraries(final ClassLoader classLoader, final String... paths) {
		for (final String path : paths) {
			try {
				ClassPathTools.addLibrary((URLClassLoader) classLoader, addUrlMethod, path);
			} catch (final Exception e) {
				if (ClassPathTools.showLog)
					e.printStackTrace();
			}
		}
	}

	static final void attachLibraries(final URLClassLoader classLoader, final String basePath) {
		final File pathFile = new File(basePath, ClassPathTools.libraryFile);
		if (!pathFile.exists()) {
			if (ClassPathTools.showLog)
				System.err.println(ClassPathTools.libraryFile + " file dosen't exists:" + pathFile);
			return;
		}
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(new FileInputStream(pathFile)));
			String dataLine = null;
			do {
				dataLine = reader.readLine();
				if (null == dataLine)
					break;
				ClassPathTools.addLibrary(classLoader, addUrlMethod, dataLine);
			} while (true);
		} catch (final Exception e) {
			if (ClassPathTools.showLog)
				e.printStackTrace();
		} finally {
			if (null != reader)
				try {
					reader.close();
				} catch (final IOException e) {
				}
		}
	}

	static final void addLibrary(final URLClassLoader classLoader, final Method addUrlMethod, final String dataLine) {
		try {
			final String path = dataLine.toLowerCase();
			if (new File(path).isDirectory()) {
				ClassPathTools.addDirectoryLibraries(classLoader, addUrlMethod, new File(dataLine));
			} else if (path.startsWith("ftp") || path.startsWith("http"))
				addUrlMethod.invoke(classLoader, new URL(path));
			else if (path.endsWith(".jar") || path.endsWith(".zip"))
				addUrlMethod.invoke(classLoader, new File(path).toURI().toURL());
		} catch (final Exception e) {
			if (ClassPathTools.showLog)
				e.printStackTrace();
		}
	}

	public static final FileFilter JAR_FILTER = new FileFilter() {
		@Override
		public boolean accept(final File filePath) {
			if (filePath.isDirectory())
				return true;
			final String fileName = filePath.getName().toLowerCase();
			return fileName.endsWith(".jar") || fileName.endsWith(".zip");
		}
	};

	static final void addDirectoryLibraries(final URLClassLoader classLoader, final Method addUrlMethod, final File directory) {
		final File[] files = directory.listFiles(ClassPathTools.JAR_FILTER);
		if (null == files)
			return;
		for (final File file : files) {
			if (file.isDirectory())
				ClassPathTools.addDirectoryLibraries(classLoader, addUrlMethod, file);
			else {
				try {
					addUrlMethod.invoke(classLoader, file.toURI().toURL());
				} catch (final Exception e) {
					if (ClassPathTools.showLog)
						e.printStackTrace();
				}
			}
		}
	}
}